use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use crate::errors::{Result, DbErrors};
use crate::fio::IoManager;
use log::error;
use parking_lot::RwLock;

#[cfg(windows)]
use std::os::windows::fs::FileExt;
#[cfg(unix)]
use std::os::unix::fs::FileExt;


pub struct FileIo{
    fd: Arc<RwLock<File>>,
}

impl FileIo {
    
    pub fn new(file_name: PathBuf) -> Result<Self>{
        match OpenOptions::new()
                .create(true)
                .read(true)
                .write(true)
                .append(true)
                .open(file_name){
                    Ok(file) => {
                        return Ok(FileIo{fd: Arc::new(RwLock::new(file))})
                    },
                    Err(e) => {
                        error!("failed to open data file: {}", e);
                        return Err(DbErrors::FailToOpenDataFile);
                    },
                }
        
    }
}

impl IoManager for FileIo {

    fn read(&self, buf: &mut [u8], offset: u64) -> Result<usize>  {
        let read = self.fd.read();

        #[cfg(unix)]
        match read.read_at(buf, offset) {
            Ok(n) => {return Ok(n);},
            Err(e) => {
                error!("read from data file err: {}", e);
                return Err(DbErrors::FailedReadFromDataFile)
            },
        }

        #[cfg(windows)]
        match read.seek_read(buf, offset) {
            Ok(n) => Ok(n),
            Err(e) => {
                error!("read from data file err: {}", e);
                Err(DbErrors::FailedReadFromDataFile)
            }
        }

    }

    fn write(&self, buf: &[u8]) -> Result<usize>  {
        let mut wr = self.fd.write() ;
        match wr.write(buf) {
            Ok(n) => {
                return Ok(n);
            }, 
            Err(e) => {
                error!("write to data file err: {}", e);
                return Err(DbErrors::FailToWriteDataFile);
            },
        }
    }


    fn sync(&self) -> Result<()>  {
        let rd = self.fd.read();
        if let Err(e) = rd.sync_all() {
            error!("failed to sync data file: {}", e);
            return Err(DbErrors::FailedSyncDataFile);
        }
        Ok(())
    }
}


#[cfg(test)]
mod tests {
    use std::fs;

    use super::*;

    #[test]
    fn test_file_io_write() {
        let path = PathBuf::from("./tmp/a.data");
        let fio_res = FileIo::new(path.clone());
        assert!(fio_res.is_ok());
        let fio = fio_res.ok().unwrap();

        let res1 = fio.write("key-a".as_bytes());
        assert!(res1.is_ok());
        assert_eq!(5, res1.ok().unwrap());

        let res2 = fio.write("key-b".as_bytes());
        assert!(res2.is_ok());
        assert_eq!(5, res2.ok().unwrap());

        let res3 = fs::remove_file(path.clone());
        assert!(res3.is_ok());
    }

    #[test]
    fn test_file_io_read() {
        let path = PathBuf::from("./tmp/b.data");
        let fio_res = FileIo::new(path.clone());
        assert!(fio_res.is_ok());
        let fio = fio_res.ok().unwrap();

        let res1 = fio.write("key-a".as_bytes());
        assert!(res1.is_ok());
        assert_eq!(5, res1.ok().unwrap());

        let res2 = fio.write("key-b".as_bytes());
        assert!(res2.is_ok());
        assert_eq!(5, res2.ok().unwrap());

        let mut buf = [0u8; 5];
        let read_res1 = fio.read(&mut buf, 0);
        assert!(read_res1.is_ok());
        assert_eq!(5, read_res1.ok().unwrap());

        let mut buf2 = [0u8; 5];
        let read_res2 = fio.read(&mut buf2, 5);
        assert!(read_res2.is_ok());
        assert_eq!(5, read_res2.ok().unwrap());

        let res3 = fs::remove_file(path.clone());
        assert!(res3.is_ok());
    }

    #[test]
    fn test_file_io_sync() {
        let path = PathBuf::from("./tmp/c.data");
        let fio_res = FileIo::new(path.clone());
        assert!(fio_res.is_ok());
        let fio = fio_res.ok().unwrap();

        let res1 = fio.write("key-a".as_bytes());
        assert!(res1.is_ok());
        assert_eq!(5, res1.ok().unwrap());

        let res2 = fio.write("key-b".as_bytes());
        assert!(res2.is_ok());
        assert_eq!(5, res2.ok().unwrap());

        let sync_res = fio.sync();
        assert!(sync_res.is_ok());

        let res3 = fs::remove_file(path.clone());
        assert!(res3.is_ok());
    }
}